/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TopCoder.Util.Calendar.Entry;

namespace TopCoder.WinForms.Controls.CalendarView
{
    /// <summary>
    /// <para>This class is an abstract base class for different calendar views. This class handles setting up a
    /// drawing area for the individual views, as well as rendering the title panel, title label, and date
    /// navigation buttons, namely for previous / next navigation through date ranges. This class defines two
    /// abstract methods for handling the next / previous navigation. The subclasses are expected to override
    /// the <see cref="OnDrawingPanelPaint(Object,PaintEventArgs)"/> method and use the <see cref="DrawingPanel"/>
    /// to render the calendar view.
    /// </para>
    /// </summary>
    /// <remarks>Thread Safety: This class is mutable, as its state can change after instantiation.</remarks>
    /// <author>Ghostar</author>
    /// <author>yext</author>
    /// <author>argolite</author>
    /// <author>alex.dolin</author>
    /// <version>1.1</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public abstract class AbstractCalendarView : UserControl
    {
        /// <summary>
        /// This panel holds the header controls, like the title label, next and previous buttons. This value is
        /// initialized in the constructor and has a Dock property value of DockStyle.Top, as it is always rendered at
        /// the top of the control. This value doesn't change after instantiation. It can't be null.
        /// </summary>
        private readonly Panel headerPanel;

        /// <summary>
        /// This member variable represents the button that tells the view to show the dates after the ones being
        /// shown. For instance, if the next button is clicked on a weekly calendar view, the week after the week
        /// currently being displayed will be displayed. This value is set in the constructor and it can't change
        /// after instantiation. The "OnNextButtonClick" method is registered as an event handler for this variables
        /// "Click" event. It can't be null.
        /// </summary>
        private readonly Button nextButton;

        /// <summary>
        /// This member variable represents the button that tells the view to show the dates before the ones being
        /// shown. For instance, if the previous button is clicked on a weekly calendar view, the week before the week
        /// currently being displayed will be displayed. This value is set in the constructor and it can't change
        /// after instantiation. The "OnPreviousButtonClick" method is registered as an event handler for this
        /// variables "Click" event. It can't be null.
        /// </summary>
        private readonly Button previousButton;

        /// <summary>
        /// This member variable holds the title text of the calendar display. The date or date ranges shown in the
        /// calendar view are displayed in this label. Each individual calendar view modifies the text of this label
        /// through the TitleLabelText property. It can't be null.
        /// </summary>
        private readonly Label titleLabel;

        /// <summary>
        /// This panel holds the drawingPanel, which each individual implementation uses to draw the calendar view.
        /// This panel is added to the control with a Dock property of DockStyle.Fill, to basically fill up any space
        /// the header panel isn't taking. This panel contains a single control: the drawingPanel member variable. We
        /// use two separate panels so that the individual views don't have to worry about adding scroll bars. This
        /// member variable is set up to display scroll bars when necessary, so all the implementation has to do is
        /// worry about drawing. This value is initialized in the constructor and doesn't change after instantiation.
        /// It can't be null.
        /// </summary>
        private readonly Panel holderPanel;

        /// <summary>
        /// This panel is a subcontrol of the holderPanel, and it represents the area where the actual calendar data is
        /// displayed. This value is initialized to a new panel in the constructor, and it is retrieved through the
        /// DrawingPanel property for use by the subclasses. They draw directly to this panel using
        /// DrawingPanel.CreateGraphics to get a Graphics instance to draw with. This value is initialized to a blank,
        /// white canvas in the constructor, but implementations can set it directly or draw to it. It can't be null.
        /// </summary>
        private Panel drawingPanel;

        /// <summary>
        /// This member variable holds the current data currently being displayed in the calendar view. This member
        /// variable is here to help the implementations as they draw and redraw the calendar data in their view
        /// specific way. This is a cache of sorts, allowing the implementations to not have to grab calendar data
        /// every time they need to redraw the calendar view, which could be expensive in the long run. Instead, they
        /// only have to grab calendar data every time the data in the view is changed, like when the "next" or
        /// "previous" button is pressed. This value is mutable, as it can be changed through the CurrentData property.
        /// It can contain null values and is initialized to an empty List. It can't be null.
        /// </summary>
        private IList<CalendarEntry> currentData = new List<CalendarEntry>();

        /// <summary>
        /// This member variable holds the data currently available for display in the calendar view. This member
        /// variable is used by the implementations when getting the data to display in each view. currentData holds a
        /// subset of the values in this collection, specific to a date range. This value is set by the
        /// CalendarViewControl when the user sets the CalendarViewControl.Data property. This value is mutable, as it
        /// can be changed through the AllData property. It cannot contain null values and is initialized to an empty
        /// List. It can't be null.
        /// </summary>
        private IList<CalendarEntry> allData = new List<CalendarEntry>();

        /// <summary>
        /// This member variable holds a mapping between specific types of calendar entries and specific colors,
        /// allowing the user to color entries in the display as they see fit. This IDictionary is initially empty, but
        /// it is modifiable through the SetColor method, which is called by the CalendarViewControl parent class, and
        /// can also be called by the user directly.
        /// </summary>
        private readonly IDictionary<int, Color> colors = new Dictionary<int, Color>();

        /// <summary>
        /// This member variable is a pointer back to the parent control, used by the view
        /// implementations' navigation panels to change the view via its navigation control.
        /// This value can't be null. It is set in the constructor and accessed in the property Parent.
        /// </summary>
        private CalendarViewControl parent;

        /// <summary>
        /// Represents a setting as to whether columns will be truncated so there is no horizontal scorllbar,
        /// or if info must be supplied in completeness, even if horizontal bars are to be employed.
        /// By default it truncates.
        /// Managed via the ColumnWidthRendering property.
        /// </summary>
        private ColumnWidthRenderingStyle columnWidthRendering = ColumnWidthRenderingStyle.Truncating;

        /// <summary>
        /// Represents the date around which this view should be built. The resulting view must inlcude this date.
        /// By default, the curent date is used. Managed via the DateOfInterest property
        /// </summary>
        private DateTime dateOfInterest = DateTime.Now;

        /// <summary>
        /// <para>This property is used by the implementations to set the title text. This property should
        /// modify and retrieve the text of the title label.</para>
        /// </summary>
        /// <exception cref="ArgumentNullException">If the given parameter is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">If the given parameter is an empty string.</exception>
        /// <value>The text of the title label.</value>
        protected string TitleLabelText
        {
            get
            {
                return titleLabel.Text;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(
                        "TitleLabelText", "TitleLabelText should not be set to null.");
                }
                if (value.Trim().Length == 0)
                {
                    throw new ArgumentException(
                        "TitleLabelText should not be set to an empty string.", "TitleLabelText");
                }

                titleLabel.Text = value;
            }
        }

        /// <summary>
        /// <para>This property allows access to the <c>NextButton</c> for the implementations. This is
        /// here so the implementations can either hide or disable the next button if the user has reached the
        /// last of the date data, and no more data is available for display. This is an optional feature that
        /// implementations can choose to not implement.</para>
        /// </summary>
        /// <value>The button used to go to the next page.</value>
        protected Button NextButton
        {
            get
            {
                return nextButton;
            }
        }

        /// <summary>
        /// <para>This property allows access to the <c>PreviousButton</c> for the implementations. This
        /// is here so the implementations can either hide or disable the previous button if the user has
        /// reached the start of the date data, and no more previous data is available for display. This is an
        /// optional feature that implementations can choose to not implement.</para>
        /// </summary>
        /// <value>The button used to go to the previous page.</value>
        protected Button PreviousButton
        {
            get
            {
                return previousButton;
            }
        }

        /// <summary>
        /// <para>This property is used by the implementations to get the area where the actual calendar data is
        /// drawn, in the implementation specific manner. This value can't be set to null, but it can be resized,
        /// if the implementation needs more space.</para>
        /// </summary>
        /// <exception cref="ArgumentNullException">If the given value is <c>null</c>.</exception>
        /// <value>The area where the actual calendar data is drawn.</value>
        protected Panel DrawingPanel
        {
            get
            {
                return drawingPanel;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(
                        "DrawingPanel", "DrawingPanel should not be set to null.");
                }

                // Clear the holder panel.
                holderPanel.Controls.Clear();

                // Add the event for painting.
                drawingPanel = value;
                drawingPanel.Paint += new PaintEventHandler(OnDrawingPanelPaint);

                // Add the value to the holder panel.
                holderPanel.Controls.Add(drawingPanel);
            }
        }

        /// <summary>
        /// <para>This property modifies the current data. Implementations can use this <c>IList</c> to
        /// cache current date data so they don't have to request date data every time the control has to be
        /// redrawn, instead only getting new data when a new date range is to be displayed, like when the next
        /// or previous button is pressed.</para>
        /// </summary>
        /// <value>The current data.</value>
        /// <exception cref="ArgumentNullException">If the given value is <c>null</c>.</exception>
        protected IList<CalendarEntry> CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("CurrentData", "CurrentData should not be set to null.");
                }
                currentData = value;
            }
        }

        /// <summary>
        /// <para>This property allows the subclasses access to the holder panel. This allows them to get the
        /// size of the holder panel, used when sizing / resizing the drawing panel. This way the subclasses
        /// can resize the drawing panel, if necessary, or they can just set the drawing panel to the size of
        /// the holder panel.</para>
        /// </summary>
        /// <value>The holder panel.</value>
        protected Panel HolderPanel
        {
            get
            {
                return holderPanel;
            }
        }

        /// <summary>
        /// <para>This property modifies the data currently available for display in the calendar view. This property
        /// is set by the <see cref="CalendarViewControl"/> to let each view know what calendar data it has
        /// available for display.</para>
        /// </summary>
        /// <exception cref="ArgumentNullException">If the given value is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">If any values in the given value are <c>null</c>.
        /// </exception>
        /// <value>The data currently available for display in the calendar view.</value>
        public IList<CalendarEntry> AllData
        {
            get
            {
                // Return a shallow copy of the field.
                return new List<CalendarEntry>(allData);
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("AllData", "AllData should not be set to null.");
                }
                if (value.Contains(null))
                {
                    throw new ArgumentException("AllData should not have null elements.", "AllData");
                }

                // Make a shallow copy of the given value and assign the field with it.
                allData = new List<CalendarEntry>(value);

                // Click Next and Previous buttons in order to refresh the content on show.
                OnNextButtonClick(null, null);
                OnPreviousButtonClick(null, null);
            }
        }

        /// <summary>
        /// This property is a reference back to the parent control, used by the view
        /// implementations' navigation panels to change the view via its navigation control.
        /// This value is set in the constructor and can't be null.
        /// </summary>
        /// <value>A reference to the parent control.</value>
        public new CalendarViewControl Parent
        {
            get
            {
                return parent;
            }
        }

        /// <summary>
        /// Represents the date around which this view should be built. The resulting view must inlcude this date.
        /// By default, the current date is used.
        /// </summary>
        /// <value>The date around which this view should be built.</value>
        public virtual DateTime DateOfInterest
        {
            get
            {
                return dateOfInterest;
            }
            set
            {
                dateOfInterest = value;
            }
        }

        /// <summary>
        /// Represents a setting as to whether columns will be truncated so there is no horizontal scorllbar,
        /// or if info must be supplied in completeness, even if horizontal bars are to be employed.
        /// By default it truncates.
        /// </summary>
        /// <value>A setting as to whether columns will be truncated.</value>
        /// <exception cref="ArgumentException">If the passed value is not a valid enum value.</exception>
        public ColumnWidthRenderingStyle ColumnWidthRendering
        {
            get
            {
                return columnWidthRendering;
            }
            set
            {
                if(!Enum.IsDefined(typeof(ColumnWidthRenderingStyle), value))
                {
                    throw new ArgumentException(
                        "The value given for the ColumnWidthRendering property isn't a valid enum value.",
                        "ColumnWidthRendering");
                }

                columnWidthRendering = value;
            }
        }

        /// <summary>
        /// <para>This constructor sets up the control by adding the header panel and its title label, next
        /// button and previous button, as well as the holder panel and drawing panel. This method also wires
        /// the <c>Click</c> events for both the next and previous button to the proper
        /// <c>OnNextButtonClick</c> and <c>OnPreviousButtonClick</c> methods.</para>
        /// </summary>
        /// <param name="parent">The parent calendar view control.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="parent"/> is null.</exception>
        protected AbstractCalendarView(CalendarViewControl parent)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent", "Argument parent should not be null.");
            }

            this.parent = parent;

            headerPanel = new Panel();
            titleLabel = new Label();
            nextButton = new Button();
            previousButton = new Button();
            holderPanel = new Panel();
            drawingPanel = new Panel();
            headerPanel.SuspendLayout();
            holderPanel.SuspendLayout();
            SuspendLayout();

            // headerPanel
            headerPanel.Controls.Add(titleLabel);
            headerPanel.Controls.Add(nextButton);
            headerPanel.Controls.Add(previousButton);
            headerPanel.Dock = DockStyle.Top;
            headerPanel.Location = new Point(0, 0);
            headerPanel.Name = "headerPanel";
            headerPanel.Size = new Size(444, 52);
            headerPanel.TabIndex = 2;
            headerPanel.Click += new EventHandler(CalendarView_Click);

            // titleLabel
            titleLabel.Anchor = AnchorStyles.Top;
            titleLabel.AutoSize = true;
            titleLabel.Font = new Font("Microsoft Sans Serif", 12F, FontStyle.Regular, GraphicsUnit.Point, 0);
            titleLabel.Location = new Point(157, 17);
            titleLabel.Name = "titleLabel";
            titleLabel.Size = new Size(130, 20);
            titleLabel.TabIndex = 2;
            titleLabel.TextAlign = ContentAlignment.MiddleCenter;
            titleLabel.Click += new EventHandler(CalendarView_Click);

            // nextButton
            nextButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
            nextButton.Location = new Point(398, 16);
            nextButton.Name = "nextButton";
            nextButton.Size = new Size(23, 23);
            nextButton.TabIndex = 1;
            nextButton.Text = ">";
            nextButton.TextAlign = ContentAlignment.MiddleCenter;
            nextButton.UseVisualStyleBackColor = true;
            nextButton.Click += new EventHandler(OnNextButtonClick);

            // previousButton
            previousButton.Location = new Point(23, 16);
            previousButton.Name = "previousButton";
            previousButton.Size = new Size(23, 23);
            previousButton.TabIndex = 0;
            previousButton.Text = "<";
            previousButton.TextAlign = ContentAlignment.MiddleCenter;
            previousButton.UseVisualStyleBackColor = true;
            previousButton.Click += new EventHandler(OnPreviousButtonClick);

            // holderPanel
            holderPanel.AutoScroll = true;
            holderPanel.BackColor = Color.White;
            holderPanel.Controls.Add(drawingPanel);
            holderPanel.Dock = DockStyle.Fill;
            holderPanel.Name = "holderPanel";
            holderPanel.TabIndex = 3;

            // drawingPanel
            drawingPanel.Location = new Point(0, 0);
            drawingPanel.Name = "drawingPanel";
            drawingPanel.TabIndex = 0;
            drawingPanel.Paint += new PaintEventHandler(OnDrawingPanelPaint);
            drawingPanel.Click += new EventHandler(CalendarView_Click);

            // AbstractCalendarView
            AutoScaleDimensions = new SizeF(6F, 13F);
            AutoScaleMode = AutoScaleMode.Font;
            Controls.Add(holderPanel);
            Controls.Add(headerPanel);
            Name = "AbstractCalendarView";
            Size = new Size(444, 362);
            headerPanel.ResumeLayout(false);
            headerPanel.PerformLayout();

            Resize += delegate { DrawingPanel.Invalidate(); };

            holderPanel.ResumeLayout(false);
            ResumeLayout(false);
        }

        /// <summary>
        /// Processes the click event for the drawingPanel, headerPanel and titleLabel.
        /// It sets focus to drawingPanel in response to mouse click, so the mouse wheel will
        /// work for the panel.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event arguments.</param>
        private void CalendarView_Click(object sender, EventArgs e)
        {
            drawingPanel.Focus();
        }

        /// <summary>
        /// <para>This method is implemented to handle the next button being clicked. The implementations will
        /// get the appropriate date data and will redraw to the <c>DrawingPanel</c>. If any errors occur
        /// while retrieving calendar data from the Calendar Entry component, a
        /// <c>CalendarDataRetrievalException</c> is thrown.</para>
        /// </summary>
        /// <param name="sender">The sender button.</param>
        /// <param name="e">The event args.</param>
        protected abstract void OnNextButtonClick(object sender, EventArgs e);

        /// <summary>
        /// <para>This method is implemented to handle the previous button being clicked. The implementations will
        /// get the appropriate date data and will redraw to the <c>DrawingPanel</c>. If any errors occur
        /// while retrieving calendar data from the Calendar Entry component, a
        /// <c>CalendarDataRetrievalException</c> is thrown.</para>
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event arguments.</param>
        protected abstract void OnPreviousButtonClick(object sender, EventArgs e);

        /// <summary>
        /// <para>This method returns the color for the given entry type. If no color is found for that specific
        /// entry type, a value of <see cref="Color.Transparent"/> is returned.</para>
        /// </summary>
        /// <exception cref="ArgumentNullException">If <paramref name="entryType"/> is <c>null</c>.</exception>
        /// <param name="entryType">The entry type to get the color for.</param>
        /// <returns>The color for the given entry type.</returns>
        public Color GetColor(CalendarEntryType entryType)
        {
            if (entryType == null)
            {
                throw new ArgumentNullException("entryType", "Argument entryType should not be null.");
            }

            if (colors.ContainsKey(entryType.TypeId))
            {
                return colors[entryType.TypeId];
            }

            return Color.Transparent;
        }

        /// <summary>
        /// <para>This method sets the color for the entry type. This method allows the user to set a color for
        /// the given entry type in a single view.</para>
        /// </summary>
        /// <param name="entryType">The entry type to set the color for.</param>
        /// <param name="color">The color to use.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="entryType"/> is <c>null</c>.
        /// </exception>
        public void SetColor(CalendarEntryType entryType, Color color)
        {
            if (entryType == null)
            {
                throw new ArgumentNullException("entryType", "Argument entryType should not be null.");
            }

            colors[entryType.TypeId] = color;
        }

        /// <summary>
        /// <para>This method draws the drawing panel with the proper content. It should be implemented by the
        /// child classes.</para>
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event, containing the graphics.</param>
        protected abstract void OnDrawingPanelPaint(object sender, PaintEventArgs e);
    }
}
